#ifndef TEST_H
#define TEST_H

#include "Assert.h"
#include "Logger.h"
#include "Mock.h"

#include <vector>
#include <unordered_map>

using namespace std;

// --- Runnable ---
class Runnable {
public:
	virtual void run() = 0;
};

// --- TEST macros ---
#define BEFORE \
	SUITE_INTERN(Before)

#define AFTER \
	SUITE_INTERN(After)

#define TEST(TestName) \
	TEST_INTERN(__FILE__, TestName, this)

#define TEST_IGNORED(TestName) \
	TEST_INTERN(__FILE__, TestName, this)

#define SUITE_INTERN(SuiteType) \
class SuiteType : public Runnable { \
public: \
	SuiteType() { \
	TestApp::add##SuiteType##(__FILE__, this); \
	} \
	virtual void run(); \
}; \
static SuiteType suite##SuiteType##Object; \
void SuiteType::run()

#define TEST_INTERN(TestSuiteName, TestName, runnable) \
class TestName : public Runnable { \
public: \
	TestName() { \
		TestApp::add(TestSuiteName, #TestName, runnable); \
	} \
	virtual void run(); \
}; \
static TestName test##TestName##Object; \
void TestName::run()

struct Test {
	Test() {
		ignored = false;
		line = 0;
		runnable = NULL;
		durationInMs = 0;
		assertFailedExceptionOccured = false;
		unexpectedCallExceptionOccured = false;
		unexpectedException = false;
	}
	string name;
	bool ignored;
	string file;
	long line;
	Runnable* runnable;

	int durationInMs;

	bool assertFailedExceptionOccured;
	AssertFailedException assertFailedException;

	bool unexpectedCallExceptionOccured;
	UnexpectedCallException unexpectedCallException;

	bool unexpectedException;
};
std::ostream& operator << (std::ostream& out, const Test test);
bool operator == (const Test t1, Test t2);

class TestLogger {
public:
	virtual void printSuite(string suiteName) = 0;
	virtual void printTest(Test test) = 0;
	virtual	void end() = 0;
};

// --- TestLogger ---
class TestLoggerImpl : public TestLogger {
private:
	Logger& logger;
public:
	TestLoggerImpl(Logger& injectedLogger);

	virtual void printSuite(string suiteName);
	virtual void printTest(Test test);
	virtual	void end();
private:
	virtual void printTest(string testName);
	virtual void printError(string error);
	virtual void print(AssertFailedException assertFailedException);
	virtual void print(UnexpectedCallException unexpectedCallException, Test test);
	virtual void printTestState(string testState);
	virtual void printDuration(int durationInMs);
};

class TestContainer {
private:
	// a test suite contains a list of tests with a test name and a test object
	//        suite name -> list of (test name, test object)
	unordered_map<string, vector<Test> > tests;

	unordered_map<string, Runnable*> beforeContainer;
	unordered_map<string, Runnable*> afterContainer;
public:
	void addTest(string suiteName, Test test);
	void addBefore(string suiteName, Runnable* runnable);
	void addAfter(string suiteName, Runnable* runnable);

	vector<string> findSuites();
	vector<Test> findTestsBySuite(string suiteName);
	Runnable* findBeforeBySuite(string suiteName);
	Runnable* findAfterBySuite(string suiteName);
private:
	Runnable* findRunnableBySuite(string suiteName, unordered_map<string, Runnable*> container);
};

// --- TestRunner ---
class TestRunner {
private:
	TestLogger& testLogger;
public:
	TestRunner(TestLogger& injectedTestLogger);
	void run(TestContainer testContainer);
};

// --- TestApp ---
class TestApp {
private:
	static TestApp& getInstance() {
		static TestApp instance;
		return instance;
	}
	TestContainer testContainer;
public:
	static void add(string testSuiteName, string testName, Runnable* runnable);
	static void addBefore(string testSuiteName, Runnable* runnable);
	static void addAfter(string testSuiteName, Runnable* runnable);
	static void run();
};

#endif